package hu.bme.mit.rifl.business.shared;

import hu.bme.mit.rifl.business.base.WFElement;
import hu.bme.mit.rifl.business.monitor.FinishedOrderMonitor;
import hu.bme.mit.rifl.business.nodes.AcknowledgeOrder;
import hu.bme.mit.rifl.business.nodes.BookTransaction;
import hu.bme.mit.rifl.business.nodes.CheckRepo;
import hu.bme.mit.rifl.business.nodes.EnqueueOrder;
import hu.bme.mit.rifl.business.nodes.FulfillOrder;
import hu.bme.mit.rifl.business.nodes.RejectOrder;
import hu.bme.mit.rifl.business.nodes.ServeOrder;
import hu.bme.mit.rifl.business.nodes.SubmitOrder;
import hu.bme.mit.rifl.datamodel.Order;

import java.util.Collection;
import java.util.PriorityQueue;

/**
 * The central coordinator node of the workflow. Stores all the input queues
 * (java.util.PriorityQueue) of the worker nodes.
 */
public class Coordinator implements Runnable {

	// input queues for business nodes
	private volatile PriorityQueue<Order> submitQueue;
	private volatile PriorityQueue<Order> acknowledgeQueue;
	private volatile PriorityQueue<Order> enqueueQueue;
	private volatile PriorityQueue<Order> checkRepoQueue;
	private volatile PriorityQueue<Order> rejectQueue;
	// private volatile PriorityQueue<Order> serveQueue;
	// private volatile PriorityQueue<Order> bookQueue;
	private volatile PriorityQueue<Order> bookAndServeQueue;
	private volatile PriorityQueue<Order> fulfillQueue;

	private WFElement submitOrder;
	private WFElement acknowledgeOrder;
	private WFElement enqueueOrder;
	private WFElement checkRepo;
	private WFElement rejectOrder;
	private WFElement serveOrder;
	private WFElement bookTransaction;
	private WFElement fulfillOrder;

	// input queues for monitor nodes
	public PriorityQueue<Order> finishedOrderQueue;
	private WFElement finishedOrderMonitor;

	/**
	 * Default constructor. Instantiates the input queues and starts the threads
	 * of the worker nodes and the monitoring node.
	 */
	public Coordinator() {
		this.submitQueue = new PriorityQueue<Order>();
		this.acknowledgeQueue = new PriorityQueue<Order>();
		this.enqueueQueue = new PriorityQueue<Order>();
		this.checkRepoQueue = new PriorityQueue<Order>();
		this.rejectQueue = new PriorityQueue<Order>();
		// this.serveQueue = new PriorityQueue<Order>();
		// this.bookQueue = new PriorityQueue<Order>();
		this.bookAndServeQueue = new PriorityQueue<Order>();
		this.fulfillQueue = new PriorityQueue<Order>();

		this.submitOrder = new SubmitOrder(this);
		this.acknowledgeOrder = new AcknowledgeOrder(this);
		this.enqueueOrder = new EnqueueOrder(this);
		this.checkRepo = new CheckRepo(this);
		this.rejectOrder = new RejectOrder(this);
		this.serveOrder = new ServeOrder(this);
		this.bookTransaction = new BookTransaction(this);
		this.fulfillOrder = new FulfillOrder(this);

		Thread TSubmit = new Thread(submitOrder);
		TSubmit.start();
		Thread TAcknowledge = new Thread(acknowledgeOrder);
		TAcknowledge.start();
		Thread TEnqueue = new Thread(enqueueOrder);
		TEnqueue.start();
		Thread TCheckRepo = new Thread(checkRepo);
		TCheckRepo.start();
		Thread TReject = new Thread(rejectOrder);
		TReject.start();
		Thread TServe = new Thread(serveOrder);
		TServe.start();
		Thread TBook = new Thread(bookTransaction);
		TBook.start();
		Thread TFulfill = new Thread(fulfillOrder);
		TFulfill.start();

		this.finishedOrderQueue = new PriorityQueue<Order>();
		this.finishedOrderMonitor = new FinishedOrderMonitor(this);
		Thread TFinished = new Thread(finishedOrderMonitor);
		TFinished.start();
	}

	/**
	 * Puts a list of work item (Orders) into the input queue of the first
	 * worker node.
	 * 
	 * @param orders
	 *            test data for the workflow
	 */
	public void assignOrders(Collection<Order> orders) {
		submitQueue.addAll(orders);
	}

	/**
	 * Overridden method of the Runnable interface.
	 */
	@Override
	public void run() {
	}

	/**
	 * Returns the next element from the input queue of the caller worker node.
	 * 
	 * @param source
	 *            name of the worker node which called for its next element
	 * @return the next work item for the caller worker node
	 */
	public Order nextOrder(WFElement source) {
		String elementType = source.getClass().getSimpleName().toLowerCase();
		if (elementType.equalsIgnoreCase("SubmitOrder")) {
			return submitQueue.poll();
		} else if (elementType.equalsIgnoreCase("AcknowledgeOrder")) {
			return acknowledgeQueue.poll();
		} else if (elementType.equalsIgnoreCase("EnqueueOrder")) {
			return enqueueQueue.poll();
		} else if (elementType.equalsIgnoreCase("CheckRepo")) {
			return checkRepoQueue.poll();
		} else if (elementType.equalsIgnoreCase("RejectOrder")) {
			return rejectQueue.poll();
		} else if (elementType.equalsIgnoreCase("ServeOrder")) {
			// return serveQueue.poll();
			Order next = bookAndServeQueue.peek();
			if (next != null && next.getState().equalsIgnoreCase("FEASIBLE"))
				return bookAndServeQueue.poll();
		} else if (elementType.equalsIgnoreCase("BookTransaction")) {
			// return bookQueue.poll();
			Order next = bookAndServeQueue.peek();
			if (next != null && !next.isBooked())
				return bookAndServeQueue.poll();
		} else if (elementType.equalsIgnoreCase("FulfillOrder")) {
			return fulfillQueue.poll();
		}
		return null;
	}

	/**
	 * A uniform interface where the processed work items can be returned to the
	 * Coordinator from the single worker nodes. After executing the business
	 * logic, every worker node returns his work product (an Order) here.
	 * 
	 * @param source
	 *            name of the worker node which tries to return the processed
	 *            work item
	 */
	public void returnOrder(WFElement source) {
		String elementType = source.getClass().getSimpleName().toLowerCase();
		if (elementType.equalsIgnoreCase("SubmitOrder")) {
			acknowledgeQueue.add(source.getOrder());
		} else if (elementType.equalsIgnoreCase("AcknowledgeOrder")) {
			enqueueQueue.add(source.getOrder());
		} else if (elementType.equalsIgnoreCase("EnqueueOrder")) {
			checkRepoQueue.add(source.getOrder());
		} else if (elementType.equalsIgnoreCase("CheckRepo")) {
			if (source.getOrder().getState().equalsIgnoreCase("FEASIBLE")) {
				bookAndServeQueue.add(source.getOrder());
				// serveQueue.add(source.getOrder());
			} else {
				rejectQueue.add(source.getOrder());
			}
		} else if (elementType.equalsIgnoreCase("RejectOrder")) {
			finishedOrderQueue.add(source.getOrder());
		} else if (elementType.equalsIgnoreCase("ServeOrder")) {
			// bookQueue.add(source.getOrder());
			Order returned = source.getOrder();
			if (returned.isBooked())
				fulfillQueue.add(returned);
			else
				bookAndServeQueue.add(returned);
		} else if (elementType.equalsIgnoreCase("BookTransaction")) {
			// fulfillQueue.add(source.getOrder());
			Order returned = source.getOrder();
			if (returned.getState().equalsIgnoreCase("GETTINGSERVED"))
				fulfillQueue.add(returned);
			else
				bookAndServeQueue.add(returned);
		} else if (elementType.equalsIgnoreCase("FulfillOrder")) {
			finishedOrderQueue.add(source.getOrder());
		}
	}

	/**
	 * Returns the queue containing the finished Orders, i.e. the ones already
	 * passed through the entire workflow.
	 * 
	 * @return the queue of the finished Orders
	 */
	public PriorityQueue<Order> getFinishedQueue() {
		return this.finishedOrderQueue;
	}
}